package org.eece262.taskshareserver;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import javax.jdo.PersistenceManager;
import javax.servlet.http.*;

import org.edu.eece262.taskshare.Messages;
import org.edu.eece262.taskshare.Messages.ActionType;
import org.edu.eece262.taskshare.Messages.MessageToClient;
import org.edu.eece262.taskshare.Messages.ResponseType;
import org.eece262.taskshareserver.Change.EntryType;
import org.eece262.taskshareserver.GroupMembership.RollType;
import org.eece262.taskshareserver.GroupMembership.StatusType;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;

public class AndroidTaskShareServlet extends HttpServlet {
	private static final long serialVersionUID = -6770100562009325024L;
	
	private PersistenceManager pm;

	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		response.setContentType("text/plain");
		try {
			response.getWriter().println(
					"TaskShare does not respond to GET method.");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) {
		
		Messages.MessageToClient messageToClient = null; 
		Messages.MessageToServer messageToServer = null;
		
		String JSONMessageToServer = request.getParameter("JSONMessageToServer");
		
		if(JSONMessageToServer == null) {
			try {
				response.getWriter().println("Error - no MessageToServer.");
			} catch (IOException e) {
				e.printStackTrace();
			}
			return;
		}
		
		try {
			Gson gson = new Gson();
			messageToServer = gson.fromJson(JSONMessageToServer, Messages.MessageToServer.class);
		} catch(JsonParseException e) {
			try {
				response.getWriter().println("Error - parsing JSON.");
			} catch (IOException e2) {
				e2.printStackTrace();
			}
			return;
		}
		
		pm = PMF.get().getPersistenceManager();
		CleanseUUIDs();
			
		switch(messageToServer.getAction()) {
			case CREATEUSER: 				
				messageToClient = createUser(messageToServer);
				break;
					
			case LOGIN: 
				messageToClient = logIn(messageToServer);
				break;
			
			case LOGOUT:
				messageToClient = logout(messageToServer);
				break;
					
			// All things that require the user to be logged in
			default:
			{
				// check if user is logged-in
				if(isLoggedIn(messageToServer.getUser(), messageToServer.getUuid())) {
					switch(messageToServer.getAction()) {
						
						case POLL: 
							messageToClient = poll(messageToServer);
							break;
						
						////////////////////
						// TASK MANAGEMENT//
						////////////////////
							case ADDTASK: 
								messageToClient = addTask(messageToServer);
								break;	
							
							case UPDATETASK: 
								messageToClient = updateTask(messageToServer);
								break;
							
							case ASSIGNTASK:
								messageToClient = assignTask(messageToServer);
								break;
							
							case ASSIGNTOGROUPONESHOT:
								messageToClient = assignToGroupOneShot(messageToServer);
								break;
							
							case ASSIGNTOGROUPMULTIPLECOMPLETION: 
								messageToClient = assignToGroupMultipleCompletion(messageToServer);
								break;
							
							case DELETETASK:
								deleteTask(messageToServer);
								break;
							
							case GETTASKS: 
								messageToClient = getTasks(messageToServer);
								break;
							
						////////////////////////
						// CONTACT MANAGEMENT //
						////////////////////////
							case SEARCHCONTACT: 
								messageToClient = searchContacts(messageToServer);
								break;
							
							case ADDCONTACT:
								messageToClient = addContact(messageToServer);
								break;
							
							case DELETECONTACT: 
								messageToClient = deleteContact(messageToServer);
								break;
							
							case GETCONTACTS: 
								messageToClient = getContacts(messageToServer);
								break;
							
						////////////////////////
						// GROUP MANAGEMENT //
						////////////////////////
							case SEARCHGROUPS: 
								messageToClient = searchGroups(messageToServer);
								break;
							
							case CREATEGROUP:
								messageToClient = addGroup(messageToServer);
								break;
							
							case JOINGROUP: 
								messageToClient = joinGroup(messageToServer);
								break;
							
							case MODIFYGROUP: 
								messageToClient = modifyGroup(messageToServer);
								break;
								
							case DELETEGROUP: 
								messageToClient = deleteGroup(messageToServer);
								break;
					}
				} else {
					messageToClient = new MessageToClient().setAction(messageToServer.getAction()).setResponse(ResponseType.FAILURENOTLOGGEDIN);
				}
				
			}	
			break;
		}
		
		pm.close();
			
		if(messageToClient == null) {
			messageToClient = new MessageToClient().setAction(messageToServer.getAction()).setResponse(Messages.ResponseType.FAILURE);
		}
		
		messageToClient.setUser(messageToServer.getUser());
		
		try {
			Gson gson = new Gson();
			response.getWriter().print(gson.toJson(messageToClient));
		} catch(JsonParseException e) {
			try {
				response.getWriter().println("Error - parsing JSON.");
			} catch (IOException e2) {
				e2.printStackTrace();
			}
			return;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@SuppressWarnings("unchecked")
	private void CleanseUUIDs() {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		//Logins only remain valid for 15 minutes
		long compareTime = System.currentTimeMillis() - 60000*15;  
		
		String query = "SELECT FROM " + UserUUIDPair.class.getName() + 
		" WHERE time < " + compareTime;
		
		List<UserUUIDPair> users = (List<UserUUIDPair>) pm.newQuery(query).execute();
		
		for(int i = 0; i < users.size(); i++) {
			pm.deletePersistent(users.get(i));
		}
	}
	
	@SuppressWarnings("unchecked")
	private boolean isLoggedIn(String user, String uuid) {
		PersistenceManager pm = PMF.get().getPersistenceManager(); 
		String query = "SELECT FROM " + UserUUIDPair.class.getName() + 
		" WHERE Username == '" + user + "' && uuid == '" + uuid + "'";
		List<UserUUIDPair> users = (List<UserUUIDPair>) pm.newQuery(query).execute();
		
		if(users.isEmpty())
			return false;
		else {
			users.get(0).setTime(System.currentTimeMillis());
			return true;
		}
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient createUser(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		String query = "SELECT FROM " + User.class.getName() + 
			" WHERE Username == '" + messageToServer.getUser() + "'";
		List<User> users = (List<User>) pm.newQuery(query).execute();
		
		if(users.isEmpty()) {
			User uNew = new User(messageToServer.getUser(), messageToServer.getPassword(), 
					messageToServer.getPhone_number(), messageToServer.getEmail(), messageToServer.getName(),
					messageToServer.getAccess_level());
			pm.makePersistent(uNew);
			
			messageToClient = new MessageToClient()
				.setResponse(ResponseType.SUCCESS)
				.setUser(messageToServer.getUser())
				.setAction(ActionType.CREATEUSER);
		} else {
			messageToClient = new MessageToClient()
				.setResponse(ResponseType.FAILURE)
				.setUser(messageToServer.getUser())
				.setAction(ActionType.CREATEUSER);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient logIn(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		
		String query = "SELECT FROM " + User.class.getName() + 
			" WHERE Username == '" + messageToServer.getUser() + "'";
		List<User> users = (List<User>) pm.newQuery(query).execute();
		
		if(users.isEmpty()) {
			messageToClient = new MessageToClient()
				.setResponse(ResponseType.FAILURE)
				.setUser(messageToServer.getUser())
				.setAction(ActionType.LOGIN);
		} else if (users.get(0).getPassword().equals(messageToServer.getPassword())) {
			UUID userLogin = UUID.randomUUID();
			UserUUIDPair userUUID = new UserUUIDPair(messageToServer.getUser(), userLogin.toString());
			pm.makePersistent(userUUID);
			messageToClient = new MessageToClient()
			.setResponse(ResponseType.SUCCESS)
			.setUser(messageToServer.getUser())
			.setAction(ActionType.LOGIN)
			.setUuid(userLogin.toString());
		} else {
			messageToClient = new MessageToClient()
				.setResponse(ResponseType.FAILURE)
				.setUser(messageToServer.getUser())
				.setAction(ActionType.LOGIN);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient logout(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		String query = "SELECT FROM " + UserUUIDPair.class.getName() + 
			" WHERE user == '" + messageToServer.getUser() + "' && uuid == '" 
			+ messageToServer.getUuid() + "'";
		
		List<UserUUIDPair> users = (List<UserUUIDPair>) pm.newQuery(query).execute();
		
		if(users.size() > 0) {
			for(int i = 0; i < users.size(); i++) {
				pm.deletePersistent(users.get(i));
			}
			messageToClient = new MessageToClient().setAction(ActionType.LOGOUT).setResponse(ResponseType.SUCCESS); 
		} else {
			messageToClient = new MessageToClient().setAction(ActionType.LOGOUT).setResponse(ResponseType.FAILURENOTLOGGEDIN);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient poll(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		messageToClient = new MessageToClient()
		.setAction(ActionType.POLL)
		.setLastModified(System.currentTimeMillis())
		.setResponse(ResponseType.SUCCESS);
		
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeUsersAffected == '" + messageToServer.getUser() + "' && " + 
				"changeTimestamp > " + messageToServer.getLastModifiedTime()).execute();
		
		for (int i = 0; i < changeResult.size(); i++) {
			switch(changeResult.get(i).getChangeType()) {
			case Change.EntryType.TASK:
				messageToClient.addTask(changeResult.get(i).getTask().getMessagesTask());
				break;
				
			case Change.EntryType.CONTACTPAIR: {
					if(changeResult.get(i).getContactPair().getUser1().equals(messageToServer.getUser())) {
						List<User> userResults = (List<User>) pm.newQuery("SELECT FROM " + User.class.getName() + 
							" WHERE Username == '" + changeResult.get(i).getContactPair().getUser2() + "'").execute();
						switch(changeResult.get(i).getContactPair().getStatus()) {
							case  ContactPair.StatusType.REQUESTED:
								messageToClient.addRequestedContact(userResults.get(0).getMessagesUser());
								break;
								
							case  ContactPair.StatusType.DELTETED:
								messageToClient.addRemovedContact(userResults.get(0).getMessagesUser());
								break;
								
							case  ContactPair.StatusType.ACTIVE:
								messageToClient.addAddedContact(userResults.get(0).getMessagesUser());
								break;
						}
					}
				}
				break;
				
			case Change.EntryType.GROUP:
				messageToClient.addGroup(changeResult.get(i).getGroup().getMessagesGroup());
				break;
			}
		}
		
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient addTask(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		messageToClient = new MessageToClient()
			.setAction(ActionType.ADDTASK)
			.setUser(messageToServer.getUser())
			.setResponse(ResponseType.SUCCESS);
		Change newChange = null;
		while(newChange == null) {
			Task newTask = new Task(messageToServer.getTask(), messageToServer.getUser(), true);
			LinkedList<String> usersAffected = new LinkedList<String>();
			usersAffected.add(newTask.getOwner());
			newChange = new Change(Change.EntryType.TASK).setUsersAffected(usersAffected).setTask(newTask);
			pm.makePersistent(newChange);
			
			//Dumb test just to verify that all tasks have truly unique globalIDs
			List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
					" WHERE changeTask.taskGlobalID == " + newChange.getTask().getGlobalID()).execute();
			if(changeResult.size() > 1) {
				pm.deletePersistent(newChange);
				newChange = null;
			}
		}
		messageToClient.addTask(newChange.getTask().getMessagesTask().setTimestamp(newChange.getTimestamp()));
		return messageToClient;
	}
	
	
	@SuppressWarnings("unchecked")
	private MessageToClient updateTask(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.UPDATETASK)
									.setUser(messageToServer.getUser());
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeTask.taskGlobalID == " + messageToServer.getTask().getGlobal_id()).execute();
		if(changeResult.isEmpty()) {
			messageToClient.setResponse(ResponseType.FAILURE);
		} else if (!changeResult.get(0).getTask().isTaskDeleted() && (changeResult.get(0).getTask().getOwner().equalsIgnoreCase(messageToServer.getUser()) || 
				changeResult.get(0).getTask().getAssignedUsernames().contains(messageToServer.getUser()))) {
			Change newChange = new Change(Change.EntryType.TASK)
				.setUsersAffected(changeResult.get(0).getUsersAffected())
				.setTask(new Task(messageToServer.getTask(), changeResult.get(0).getTask().getOwner(), false));
			pm.makePersistent(newChange);
			pm.deletePersistent(changeResult.get(0));
		} else {
			messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient assignTask(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setUser(messageToServer.getUser()).setAction(ActionType.ASSIGNTASK);
		// Check contact status
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + 
				"' && changeContactPair.contactsUser2 == '" + messageToServer.getContact_user() + "'").execute();
		if(changeResult.size() <= 0 || changeResult.get(0).getContactPair().getStatus() != ContactPair.StatusType.ACTIVE) {
			messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);
		} else {
			// Do the reassignment
			List<Change> changeResult2 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
					" WHERE changeTask.taskGlobalID == " + messageToServer.getTask().getGlobal_id()).execute();
			if (changeResult2.get(0).getTask().getOwner().equalsIgnoreCase(messageToServer.getUser()) || 
					changeResult2.get(0).getTask().getAssignedUsernames().contains(messageToServer.getUser())) {
				Change newChange = new Change(Change.EntryType.TASK)
					.setUsersAffected(changeResult2.get(0).getUsersAffected())
					.addUsersAffected(messageToServer.getContact_user())
					.setTask(changeResult2.get(0).getTask().addAssignedUsernames(messageToServer.getContact_user()));
				pm.makePersistent(newChange);
				pm.deletePersistent(changeResult2.get(0));
			} else {
				messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);
			}
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient assignToGroupOneShot(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setUser(messageToServer.getUser()).setAction(ActionType.ASSIGNTOGROUPONESHOT);

		List<Change> groupResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeType == " + EntryType.GROUP + " && changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		
		List<Change> taskResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeTask.taskGlobalID == " + messageToServer.getTask().getGlobal_id()).execute();
		
		if(groupResult.size() > 0 && taskResult.size() > 0) {
			boolean isOwnerOrAdmin = false;
			if(groupResult.get(0).getGroup().getGroupOwner().equals(messageToServer.getUser())) {
				isOwnerOrAdmin = true;
			} else {
				for (int i = 0; i < groupResult.get(0).getGroup().getGroupMembers().size(); i++) { 
					if(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser().equals(messageToServer.getUser()) && 
							groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberRoll() == GroupMembership.RollType.ADMIN) {
						isOwnerOrAdmin = true;
					}
				}
			}
			
			if(isOwnerOrAdmin) {
				Change newChange = new Change(EntryType.TASK).setUsersAffected(taskResult.get(0).getUsersAffected());
				Task task = taskResult.get(0).getTask();
				for (int i = 0; i < groupResult.get(0).getGroup().getGroupMembers().size(); i++) { 
					if(!(taskResult.get(0).getUsersAffected().contains(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser()) && 
							taskResult.get(0).getTask().getAssignedUsernames().contains(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser())) && 
							groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberStatus() == GroupMembership.StatusType.ACTIVE) {
						newChange.addUsersAffected(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser());
						task.addAssignedUsernames(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser());
						
					}
					if(!taskResult.get(0).getUsersAffected().contains(messageToServer.getGroup().getGroupName())) {
						newChange.addUsersAffected(messageToServer.getGroup().getGroupName());
						task.addAssignedUsernames(messageToServer.getGroup().getGroupName());
					}
					messageToClient.addTask(taskResult.get(0).getTask().getMessagesTask());
					messageToClient.setResponse(ResponseType.SUCCESS);
				}
				newChange.setTask(task);
				pm.makePersistent(newChange);
				pm.deletePersistent(taskResult.get(0));
			} else {
				messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);	
			}
				
		} else {
			messageToClient.setResponse(ResponseType.FAILURE);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient assignToGroupMultipleCompletion(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setUser(messageToServer.getUser()).setAction(ActionType.ASSIGNTOGROUPMULTIPLECOMPLETION);

		List<Change> groupResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeType == " + EntryType.GROUP + " && changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		
		List<Change> taskResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeTask.taskGlobalID == " + messageToServer.getTask().getGlobal_id()).execute();
		
		if(groupResult.size() > 0 && taskResult.size() > 0) {
			boolean isOwnerOrAdmin = false;
			if(groupResult.get(0).getGroup().getGroupOwner().equals(messageToServer.getUser())) {
				isOwnerOrAdmin = true;
			} else {
				for (int i = 0; i < groupResult.get(0).getGroup().getGroupMembers().size(); i++) { 
					if(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser().equals(messageToServer.getUser()) && 
							groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberRoll() == GroupMembership.RollType.ADMIN) {
						isOwnerOrAdmin = true;
					}
				}
			}
			
			if(isOwnerOrAdmin && !taskResult.get(0).getUsersAffected().contains(groupResult.get(0).getGroup().getGroupName())) {
				for (int i = 0; i < groupResult.get(0).getGroup().getGroupMembers().size(); i++) { 
					if(!(taskResult.get(0).getUsersAffected().contains(groupResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser()))) {
						Change newChange = null; 
						while(newChange == null) {
							newChange = new Change(EntryType.TASK).setTask(taskResult.get(0).getTask()).addUsersAffected(messageToServer.getGroup().getOwner()).addUsersAffected(messageToServer.getUser());
							newChange.getTask().setName(taskResult.get(0).getTask().getName() + " - " + messageToServer.getUser());
							newChange.getTask().setGlobal_parent(taskResult.get(0).getTask().getGlobalID());
							pm.makePersistent(newChange);
							
							//Dumb test just to verify that all tasks have truly unique globalIDs
							List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
									" WHERE changeTask.taskGlobalID == " + newChange.getTask().getGlobalID()).execute();
							if(changeResult.size() > 1) {
								pm.deletePersistent(newChange);
								newChange = null;
							}
						}
					}
				}
				if(!taskResult.get(0).getUsersAffected().contains(messageToServer.getGroup().getGroupName())) {
					taskResult.get(0).addUsersAffected(messageToServer.getGroup().getGroupName());
				}
				
				taskResult.get(0).setTimestamp(System.currentTimeMillis());
				messageToClient.setResponse(ResponseType.SUCCESS);
				
			} else {
				messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);	
			}
				
		} else {
			messageToClient.setResponse(ResponseType.FAILURE);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient deleteTask(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = null;
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeTask.taskGlobalID == " + messageToServer.getTask().getGlobal_id()).execute();
		if(changeResult.size() > 0) {
			for(int i = 0; i < changeResult.size(); i++) {
				Task currentTask = changeResult.get(i).getTask();
				if(currentTask.getOwner().equals(messageToServer.getUser())) {
					currentTask.setTaskDeleted(true);
					Change newChange = new Change(Change.EntryType.TASK).setUsersAffected(currentTask.getUsersAffected()).setTask(currentTask);
					messageToClient = new MessageToClient()
					.setAction(ActionType.DELETETASK)
					.setUser(messageToServer.getUser())
					.setResponse(ResponseType.SUCCESS);
					pm.deletePersistent(changeResult.get(i));
					pm.makePersistent(newChange);
				} else {
					messageToClient = new MessageToClient()
					.setAction(ActionType.DELETETASK)
					.setUser(messageToServer.getUser())
					.setResponse(ResponseType.FAILURENOTPERMITTED);
				}
			}
				
		} else {
			messageToClient = new MessageToClient()
				.setAction(ActionType.DELETETASK)
				.setUser(messageToServer.getUser())
				.setResponse(ResponseType.FAILURE);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient getTasks(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.GETTASKS)
			.setUser(messageToServer.getUser())
			.setResponse(ResponseType.SUCCESS);
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeUsersAffected = '" + messageToServer.getUser() + "' && changeType == " + EntryType.TASK).execute();
		for(int i = 0; i < changeResult.size(); i++) {
			messageToClient.addTask(changeResult.get(i).getTask().getMessagesTask());
		}
		return messageToClient;
	}

	@SuppressWarnings("unchecked")
	private MessageToClient searchContacts(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		String contactsQuery = "SELECT FROM " + User.class.getName() + 
		" WHERE " + messageToServer.getContact_user();
		List<User> contactsResult = (List<User>) pm.newQuery(contactsQuery).execute();
		if (contactsResult.isEmpty()) {
			messageToClient = new MessageToClient()
			.setAction(ActionType.SEARCHCONTACT)
			.setContact_user(messageToServer.getContact_user())
			.setResponse(ResponseType.FAILURE);
		} else {
			LinkedList<Messages.User> results = new LinkedList<Messages.User>();
			Iterator<User> currentUserIterator =  contactsResult.iterator();
			while(currentUserIterator.hasNext()) {
				User currentUser = currentUserIterator.next();
				if (currentUser.getAccessLevel() != 0)
					results.add(currentUser.getMessagesUser());
			}
			messageToClient = new MessageToClient()
			.setAction(ActionType.SEARCHCONTACT)
			.setContact_user(messageToServer.getContact_user())
			.setResponse(ResponseType.SUCCESS);
			messageToClient.setUsers(results);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient addContact(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		
		//Check for existing request
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + "' && changeContactPair.contactsUser2 == '" + messageToServer.getContact_user() + "'").execute();
	
		messageToClient = new MessageToClient()
		.setAction(ActionType.ADDCONTACT)
		.setContact_user(messageToServer.getContact_user())
		.setResponse(ResponseType.ONHOLD);
		// If so, send back an acceptances in both directions
		if(changeResult != null && changeResult.size() > 0 && changeResult.get(0).getContactPair().getStatus() == ContactPair.StatusType.REQUESTED) {
			//Remove old entries
			for(int i = 0; i < changeResult.size(); i++) {
				pm.deletePersistent(changeResult.get(i));
			}
			List<Change> changeResult2 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
					" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getContact_user() + "' && changeContactPair.contactsUser2 == '" + messageToServer.getUser() + "'").execute();
			for(int i = 0; i < changeResult2.size(); i++) {
				pm.deletePersistent(changeResult2.get(i));
			}
			
			ContactPair newContactPair = new ContactPair(messageToServer.getUser(), messageToServer.getContact_user(), ContactPair.StatusType.ACTIVE);
			Change change1 = new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
			.addUsersAffected(messageToServer.getContact_user())
			.setContactPair(newContactPair);
			
			ContactPair newContactPair2 = new ContactPair(messageToServer.getContact_user(), messageToServer.getUser(), ContactPair.StatusType.ACTIVE);
			Change change2 = new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
			.addUsersAffected(messageToServer.getContact_user())
			.setContactPair(newContactPair2);
			
			pm.makePersistent(change1);
			
			pm.makePersistent(change2);
			
			messageToClient.setResponse(ResponseType.SUCCESS);
		} else if(changeResult == null || changeResult.size() == 0) {
			// If not, forward the request on to the end user.
			ContactPair newContactPair = new ContactPair(messageToServer.getUser(), messageToServer.getContact_user(), ContactPair.StatusType.WAITING);
			pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
										.addUsersAffected(messageToServer.getContact_user())
										.setContactPair(newContactPair));
			ContactPair newContactPair2 = new ContactPair(messageToServer.getContact_user(), messageToServer.getUser(), ContactPair.StatusType.REQUESTED);
			pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
										.addUsersAffected(messageToServer.getContact_user())
										.setContactPair(newContactPair2));
		} else if(changeResult.size() > 0 && changeResult.get(0).getContactPair().getStatus() == ContactPair.StatusType.DELTETED) {
			// If exists but is deleted, change to a request.
			ContactPair newContactPair = new ContactPair(messageToServer.getUser(), messageToServer.getContact_user(), ContactPair.StatusType.WAITING);
			pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
										.addUsersAffected(messageToServer.getContact_user())
										.setContactPair(newContactPair));
			ContactPair newContactPair2 = new ContactPair(messageToServer.getContact_user(), messageToServer.getUser(), ContactPair.StatusType.REQUESTED);
			pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
										.addUsersAffected(messageToServer.getContact_user())
										.setContactPair(newContactPair2));
			for(int i = 0; i < changeResult.size(); i++) {
				pm.deletePersistent(changeResult.get(i));
			}
			List<Change> changeResult2 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
					" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + "' && changeContactPair.contactsUser2 == '" + messageToServer.getContact_user() + "'").execute();
			for(int i = 0; i < changeResult2.size(); i++) {
				pm.deletePersistent(changeResult2.get(i));
			}
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient deleteContact(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		ContactPair newContactPair = new ContactPair(messageToServer.getUser(), messageToServer.getContact_user(), ContactPair.StatusType.DELTETED);
		pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
									.addUsersAffected(messageToServer.getContact_user())
									.setContactPair(newContactPair));
		ContactPair newContactPair2 = new ContactPair(messageToServer.getContact_user(), messageToServer.getUser(), ContactPair.StatusType.DELTETED);
		pm.makePersistent(new Change(Change.EntryType.CONTACTPAIR).addUsersAffected(messageToServer.getUser())
									.addUsersAffected(messageToServer.getContact_user())
									.setContactPair(newContactPair2));
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + "' && changeContactPair.contactsUser2 == '" + messageToServer.getContact_user() + "'").execute();
		for(int i = 0; i < changeResult.size(); i++) {
			pm.deletePersistent(changeResult.get(i));
		}
		List<Change> changeResult2 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + "' && changeContactPair.contactsUser2 == '" + messageToServer.getContact_user() + "'").execute();
		for(int i = 0; i < changeResult2.size(); i++) {
			pm.deletePersistent(changeResult2.get(i));
		}
		messageToClient = new MessageToClient()
		.setAction(ActionType.DELETECONTACT)
		.setContact_user(messageToServer.getContact_user())
		.setResponse(ResponseType.SUCCESS);
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient getContacts(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient;
		
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeContactPair.contactsUser1 == '" + messageToServer.getUser() + "' && status == " + ContactPair.StatusType.ACTIVE).execute();
		
		messageToClient = new  MessageToClient().setAction(ActionType.GETCONTACTS).setResponse(ResponseType.SUCCESS);
		
		for(int i = 0; i < changeResult.size(); i++) {
			String userQuery = "SELECT FROM " + User.class.getName() + 
				" WHERE user1 == '" + changeResult.get(i).getContactPair().getUser2() + "'";
			
			List<User> userResult = (List<User>) pm.newQuery(userQuery).execute();
			
			messageToClient.addUser(userResult.get(0).getMessagesUser());
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient searchGroups(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.SEARCHGROUPS)
			.setUser(messageToServer.getUser())
			.setResponse(ResponseType.SUCCESS);
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeType == " + EntryType.GROUP + " && changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		for(int i = 0; i < changeResult.size(); i++) {
			messageToClient.addGroup(changeResult.get(i).getGroup().getMessagesGroup());
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient addGroup(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.CREATEGROUP)
		.setUser(messageToServer.getUser());
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeType == " + EntryType.GROUP + " && changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		if(changeResult.size() > 0) {
			messageToClient.setResponse(ResponseType.FAILURE);
		} else {
			messageToClient.setResponse(ResponseType.SUCCESS);
			Change newChange = new Change(EntryType.GROUP);
			Group group = new Group()
				.setGroupOwner(messageToServer.getUser())
				.setGroupName(messageToServer.getGroup().getGroupName());
			GroupMembership gmowner = new GroupMembership();
			gmowner.setGroupMemberRoll(RollType.OWNER);
			gmowner.setGroupMemberStatus(GroupMembership.StatusType.ACTIVE);
			gmowner.setGroupMemberUser(messageToServer.getUser());
			newChange.addUsersAffected(messageToServer.getUser());
			group.addGroupMember(gmowner);
			for(int i = 0; i < messageToServer.getGroup().getMembers().size(); i++) {
				newChange.addUsersAffected(messageToServer.getGroup().getMembers().get(i).getGroupMemberUser());
				group.addGroupMember(new GroupMembership(messageToServer.getGroup().getMembers().get(i))
					.setGroupMemberStatus(GroupMembership.StatusType.REQUESTED));
			}
			newChange.setGroup(group);
			pm.makePersistent(newChange);
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient joinGroup(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient()
			.setAction(ActionType.JOINGROUP)
			.setUser(messageToServer.getUser())
			.setResponse(ResponseType.ONHOLD);
		
		//Does group exist
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeType == " + EntryType.GROUP + " && changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		
		if(changeResult == null || changeResult.size() <= 0) {
			messageToClient.setResponse(ResponseType.FAILURE);
		} else {
			Group group = changeResult.get(0).getGroup();
			boolean existingRequest = false;
			int existingIndex = 0;

			for(int i = 0; i < group.getGroupMembers().size(); i++) {
				if(group.getGroupMembers().get(i).getGroupMemberUser().equalsIgnoreCase(messageToServer.getUser())) {
					existingRequest = true;
					existingIndex = i;
				}
			}
			
			if(existingRequest) {
				if(group.getGroupMembers().get(existingIndex).getGroupMemberStatus() == GroupMembership.StatusType.WAITING) {
					messageToClient.setResponse(ResponseType.ONHOLD);
				} else if(group.getGroupMembers().get(existingIndex).getGroupMemberStatus() == GroupMembership.StatusType.REQUESTED || 
						group.getGroupMembers().get(existingIndex).getGroupMemberStatus() == GroupMembership.StatusType.ACTIVE) {
					messageToClient.setResponse(ResponseType.SUCCESS);
					Change newEntry = new Change(EntryType.GROUP).setGroup(changeResult.get(0).getGroup()).setUsersAffected(changeResult.get(0).getUsersAffected());
					LinkedList<GroupMembership> members = new LinkedList<GroupMembership>();
					for(int i = 0; i < newEntry.getGroup().getGroupMembers().size(); i++) {
						if(newEntry.getGroup().getGroupMembers().get(i).getGroupMemberUser().equals(messageToServer.getUser())) {
							members.add(newEntry.getGroup().getGroupMembers().get(i).setGroupMemberStatus(GroupMembership.StatusType.ACTIVE));
						} else {
							members.add(newEntry.getGroup().getGroupMembers().get(i));
						}
					}
					newEntry.setGroup(newEntry.getGroup().setGroupMembers(members));
					pm.makePersistent(newEntry);
					pm.deletePersistent(changeResult.get(0));
					List<Change> changeResult2 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
							" WHERE changeType == " + EntryType.TASK + " && changeUsersAffected == '" + messageToServer.getGroup().getGroupName() + "'").execute();
					for(int j = 0; j < changeResult2.size(); j++) {
						if(!changeResult2.get(j).getUsersAffected().contains(messageToServer.getUser()) && messageToServer.getTask().getAssignedList().contains(messageToServer.getGroup().getGroupName())) {
							Change newChange = new Change(EntryType.TASK).setTask(changeResult2.get(j).getTask().addAssignedUsernames(messageToServer.getUser()));
							newChange.setUsersAffected(changeResult2.get(j).getUsersAffected());
							newChange.addUsersAffected(messageToServer.getUser());
							pm.makePersistent(newChange);
						} else {
							Change newChange = null;
							while(newChange == null) {
								newChange = new Change(EntryType.TASK).setTask(changeResult2.get(j).getTask()).addUsersAffected(messageToServer.getGroup().getOwner()).addUsersAffected(messageToServer.getUser());
								newChange.getTask().setName(changeResult2.get(j).getTask().getName() + " - " + messageToServer.getUser());
								newChange.getTask().setGlobal_parent(changeResult2.get(j).getTask().getGlobalID());
								pm.makePersistent(newChange);
								
								//Dumb test just to verify that all tasks have truly unique globalIDs
								List<Change> changeResult3 = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
										" WHERE changeTask.taskGlobalID == " + newChange.getTask().getGlobalID()).execute();
								if(changeResult3.size() > 1) {
									pm.deletePersistent(newChange);
									newChange = null;
								}
							}
						}
					}
				} else if(group.getGroupMembers().get(existingIndex).getGroupMemberStatus() == GroupMembership.StatusType.DELTETED) {
					group.getGroupMembers().get(existingIndex).setGroupMemberStatus(GroupMembership.StatusType.WAITING);
					changeResult.get(0).setTimestamp(System.currentTimeMillis());
					messageToClient.setResponse(ResponseType.ONHOLD);
				}
			} else {
				GroupMembership gm = new GroupMembership().setGroupMemberRoll(GroupMembership.RollType.USER)
					.setGroupMemberStatus(GroupMembership.StatusType.WAITING)
					.setGroupMemberUser(messageToServer.getUser());
				
				Change newEntry = new Change(EntryType.GROUP).setGroup(changeResult.get(0).getGroup()).setUsersAffected(changeResult.get(0).getUsersAffected());
				newEntry.getGroup().addGroupMember(gm);
				newEntry.addUsersAffected(messageToServer.getUser());
				newEntry.setTimestamp(System.currentTimeMillis());
				
				pm.makePersistent(newEntry);
				pm.deletePersistent(changeResult.get(0));
				messageToClient.setResponse(ResponseType.ONHOLD);
			}
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient modifyGroup(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.MODIFYGROUP)
			.setUser(messageToServer.getUser()).setResponse(ResponseType.SUCCESS);
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		if(changeResult.size() <= 0) {
			messageToClient.setResponse(ResponseType.FAILURE);
		} else {
			Group group = new Group();
			boolean isAdminOrOwner = false;
			if(changeResult.get(0).getGroup().getGroupOwner().equals(messageToServer.getUser())) {
				isAdminOrOwner = true;
				group.setGroupOwner(messageToServer.getGroup().getOwner());
			}
			if(!isAdminOrOwner) {
				for(int i = 0; i < changeResult.get(0).getGroup().getGroupMembers().size(); i ++) {
					if (changeResult.get(0).getGroup().getGroupMembers().get(i).getGroupMemberUser()
							.equals(messageToServer.getUser()) && changeResult.get(0).getGroup().getGroupMembers()
							.get(i).getGroupMemberRoll() == GroupMembership.RollType.ADMIN) {
						isAdminOrOwner = true;
						group.setGroupOwner(changeResult.get(0).getGroup().getGroupOwner());
					}
				}
			}
			if(!isAdminOrOwner) {
				messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);
			} else {
				
				group.setGroupName(changeResult.get(0).getGroup().getGroupName());
				for(int i = 0; i < messageToServer.getGroup().getMembers().size(); i ++) {
					group.addGroupMember(new GroupMembership(messageToServer.getGroup().getMembers().get(i)));
					if (!changeResult.get(0).getUsersAffected().contains(messageToServer.getGroup().getMembers().get(i).getGroupMemberUser())) {
						changeResult.get(0).addUsersAffected(messageToServer.getGroup().getMembers().get(i).getGroupMemberUser()); 
					}
				}
				changeResult.get(0).setGroup(group);
				changeResult.get(0).setTimestamp(System.currentTimeMillis());
				messageToClient.addGroup(changeResult.get(0).getGroup().getMessagesGroup());
			}
		}
		return messageToClient;
	}
	
	@SuppressWarnings("unchecked")
	private MessageToClient deleteGroup(Messages.MessageToServer messageToServer) {
		MessageToClient messageToClient = new MessageToClient().setAction(ActionType.DELETEGROUP)
			.setUser(messageToServer.getUser()).setResponse(ResponseType.SUCCESS);
		List<Change> changeResult = (List<Change>) pm.newQuery("SELECT FROM " + Change.class.getName() + 
				" WHERE changeGroup.groupName == '" + messageToServer.getGroup().getGroupName() + "'").execute();
		if(changeResult.size() <= 0) {
			messageToClient.setResponse(ResponseType.FAILURE);
		} else if(changeResult.get(0).getGroup().getGroupOwner().equals(messageToServer.getUser())) {
				changeResult.get(0).getGroup().setGroupDeleted(true);
				changeResult.get(0).setTimestamp(System.currentTimeMillis());
		} else {
			messageToClient.setResponse(ResponseType.FAILURENOTPERMITTED);
		}
		return messageToClient;
	}
}